#include "Event.h"
#include "SqlInterface.h"

#include <QDebug>

Event::Event()
    : m_top(true),
      m_shown(false)
{
    ;
}

Event::Event(const EventString &eventStr)
    : m_eventString(eventStr),
      m_top(true),
      m_shown(false)
{
    m_begin = m_eventString.toDateTime();
    m_end = m_begin.addDays(1);
}

//Event::Event(const Event &event)
//{
//    m_begin = event.begin();
//    m_end = event.end();
//    m_name = event.name();
//    m_definition = event.definition();
//    m_top = event.isTop();
//}

bool Event::operator== (const Event &event) const
{
    return (begin() == event.begin())
            && (end() == event.end())
            && (name() == event.name())
            && (definition() == event.definition());
}

bool Event::operator!= (const Event &event) const
{
    return !(*this == event);
}

QDebug &operator<< (QDebug &debug, const Event &event)
{
    debug << "Event: \n"
        << "name       = " << event.name() << "\n"
        << "begin      = " << event.begin().toString("yyyy-MM-dd") << "\n"
        << "end        = " << event.end().toString("yyyy-MM-dd") << "\n"
        << "definition = " << event.definition() << "\n"
        << "image      = " << event.imagePath() << "\n"
        << "icon       = " << event.iconPath() << "\n"
        << "color      = " << event.color() << "\n";
    return debug;
}

QDataStream &operator<< (QDataStream &stream, const Event &event)
{
    stream
            << event.name()
            << event.begin()
            << event.end()
            << event.definition()
            << event.imagePath()
            << event.iconPath()
            << event.color();
    return stream;
}

QDataStream &operator>> (QDataStream &stream, Event &event)
{
    stream
            >> event.m_name
            >> event.m_begin
            >> event.m_end
            >> event.m_definition
            >> event.m_imagePath
            >> event.m_iconPath
            >> event.m_color;
    return stream;
}

void Event::setName(const QString &name)
{
    m_name = name;
}

void Event::setDefinition(const QString &definition)
{
    m_definition = definition;
}

void Event::setImagePath(const QString &file)
{
    m_imagePath = file;
}

void Event::setIconPath(const QString &file)
{
    m_iconPath = file;
}

void Event::setColor(const QString &color)
{
    m_color = color;
}

void Event::setShown(bool show)
{
    m_shown = show;
}

QDate Event::orthodoxEasterDate(int year)
{
    int M = 15;
    int N = 6;

    int A = year % 19;
    int B = year % 4;
    int C = year % 7;
    int D = (19 * A + M) % 30;
    int E = (2 * B + 4 * C + 6 * D + N) % 7;
    int F = 4 + D + E;

    QDate easterDate = (F > 30) ? QDate(year, 5, (F - 30)) : QDate(year, 4, F);
    return easterDate;
}

QDate Event::catholicEasterDate(int year)
{
    int Q = (year / 100) - (year / 100) / 4;
    int R = (8 * (year / 100) + 13) / 25;
    int M = (15 + Q - R) % 30;
    int N = (4  +  Q) % 7;

    int A = year % 19;
    int B = year % 4;
    int C = year % 7;
    int D = (19 * A + M) % 30;
    int E = (2 * B + 4 * C + 6 * D + N) % 7;
    int F = 22 + D + E;

    int x = (F == 57 || (F == 56 && E == 6 && A > 10)) ? F = F - 7 : F;
    QDate easterDate = QDate(year, 3, 1).addDays(x - 1);
    return easterDate;
}

QDate Event::shiftEventDate(int year, int month, int week, int day, bool onlyFullWeek)
{
//    qDebug() << "year: " << year;
//    qDebug() << "month: " << month;
//    qDebug() << "week: " << week;
//    qDebug() << "day: " << day;

    // Дата из года и месяца (Например: 2011.12.01)
    QDate date = QDate(year, month, 1);
//    qDebug() << date.toString("dd-MM-yyyy");

    // День недели первого числа (2011.12.01 - четверг)
    int dayOfWeek = date.dayOfWeek();
//    qDebug() << "dayOfWeek: " << dayOfWeek;

    // Количество дней в месяце (в декабре 31 день)
    int daysInMonth = date.daysInMonth();
//    qDebug() << "daysInMonth: " << daysInMonth;

    // Узнаём количество дней оставшихся до полной недели (т.к. ЧТ, до ВС 3 дня)
    int daysToFullWeek = 7 - dayOfWeek;
//    qDebug() << "daysToFullWeek: " << daysToFullWeek;

    // Вычисляем конец первой недели (воскресенье - 2011.12.04)
    QDate firstWeekEndDate = date.addDays(daysToFullWeek);
//    qDebug() << "firstWeekEndDate: " << firstWeekEndDate.toString("dd-MM-yyyy");

    // День месяца для конца первой недели (4е число)
    int firstWeekEndDay = firstWeekEndDate.day();
//    qDebug() << "firstWeekEndDay: " <<  firstWeekEndDay;

    // Количество дней до конца месяца (27 дней)
    int lastDays = daysInMonth - firstWeekEndDay;
//    qDebug() << "lastDays: " << lastDays;
//    qDebug() << "lastDays / 7: " << lastDays / 7;

    // Первая неделя полная?
    int firstWeekIsFull = (dayOfWeek == Qt::Monday) ? 1: 0;
//    qDebug() << "firstWeekIsFull: " << firstWeekIsFull;

    // Последняя неделя полная?
    int lastWeekIsFull = (lastDays % 7 == 0) ? 1 : 0;
//    qDebug() << "lastWeekIsFull: " << lastWeekIsFull;

    // Количество полных недель в месяце (в декабре 2011 3 полных недели)
    int fullWeeksInMonth = lastDays / 7 + firstWeekIsFull;
//    qDebug() << "fullWeeksInMonth: " << fullWeeksInMonth;

    // Количество неполных недель в месяце (и 2 неполных)
    int partWeeksInMonth = !firstWeekIsFull + !lastWeekIsFull;
//    qDebug() << "partWeeksInMonth: " << partWeeksInMonth;

    // Количество недель в месяце (всего в декабре 2011 5 недель)
    int weeksInMonth = fullWeeksInMonth + partWeeksInMonth;
//    qDebug() << "weeksInMonth: " << weeksInMonth;

//    qDebug() << "int('L'): " << int('L');
//    qDebug() << "int('P'): " << int('P');

//    qDebug() << "onlyFullWeek: " << onlyFullWeek;

    // Корректируем номер недели
    if (week < 1) {
        week = 1;
    } else if (week == int('P')) {
        week = weeksInMonth - 1;
    } else if (week == int('L') || week > weeksInMonth) {
        week = weeksInMonth;
    }

    if (onlyFullWeek) {
        if (week == 1 && !firstWeekIsFull) {
            week += 1;
        }
        if (week == weeksInMonth && !lastWeekIsFull) {
            week -= 1;
        }
    }

    // Дни (день недели + номер недели)
    int days = 0;
    // Если первая неделя полная
    if (firstWeekIsFull) {
        days = (week - 1) * 7 + day - 1;
//        qDebug("days = (week(%d) - 1) * 7 + day(%d)", week, day);
    } else {
        // Если первая неделя
        // и нужный день недели попадает в эту неполную неделю
        if (week == 1) {
            if (day >= dayOfWeek) {
                days = day - dayOfWeek;
//                qDebug("days = day(%d) - dayOfWeek(%d)", day, dayOfWeek);
            } else {
                days = daysToFullWeek + day;
//                qDebug("days = daysToFullWeek(%d) + day(%d)", daysToFullWeek, day);
            }
        } else if (week == weeksInMonth) {
            days = daysToFullWeek + (week - 2) * 7 + day;
//            qDebug("days = (daysToFullWeek(%d)) + (week(%d) - 2) * 7 + day(%d)", daysToFullWeek, week, day);
            if (!lastWeekIsFull && days > daysInMonth) {
//                qDebug("days(%d) -= 7", days);
                days -= 7;
            }
        } else {
            days = daysToFullWeek + (week - 2) * 7 + day;
//            qDebug("days = (daysToFullWeek(%d)) + (week(%d) - 2) * 7 + day(%d)", daysToFullWeek, week, day);
        }

    }
//    qDebug() << "days: " << days;

    QDate resultDate = date.addDays(days);
//    qDebug() << "resultDate: " << resultDate.toString("dd-MM-yyyy");

    return resultDate;
}

// ************************************************************

EventList_t EventSchedule::getMonthSchedule(int year, int month)
{
    QSqlQuery query;
    EventListParams params(year, month);

    query.prepare("SELECT * FROM event "
                  "WHERE (format LIKE 'YYYYMM%' AND occur LIKE :dateYYYYMM) "
                  "OR (format LIKE 'RRRRMM%' AND substr(occur, 5, 2) LIKE :monthMM1) "
                  "OR (format LIKE 'MM%' AND occur LIKE :monthMM2) "
                  "OR (format LIKE 'N%' AND occur > :beginDayDDD AND occur < :endDayDDD) "
                  "OR (format LIKE 'O%' AND strftime('%m', date(:orthodoxEasterDate, '+' || ltrim(occur,'0') || ' day')) LIKE :monthMM3) "
                  "OR (format LIKE 'E%' AND strftime('%m', date(:catholicEasterDate, '+' || ltrim(occur,'0') || ' day')) LIKE :monthMM4) ");
    query.bindValue(":dateYYYYMM", params.year() + params.month() + "%");
    query.bindValue(":monthMM1", params.month() + "%");
    query.bindValue(":monthMM2", params.month() + "%");
    query.bindValue(":monthMM3", params.month() + "%");
    query.bindValue(":monthMM4", params.month() + "%");
    query.bindValue(":beginDayDDD", params.dayOfYear());
    query.bindValue(":endDayDDD", params.endDayOfYear());
    query.bindValue(":orthodoxEasterDate", Event::orthodoxEasterDate(year));
    query.bindValue(":catholicEasterDate", Event::catholicEasterDate(year));
    query.exec();

    if (query.lastError().isValid()) {
        qDebug() << query.lastError();
    }

    EventList_t eventList;
    while (query.next()) {
        QString format = query.record().value("format").toString();
        QString occur = query.record().value("occur").toString();
        QString name = query.record().value("name").toString();
        QString definition = query.record().value("definition").toString();
        QString imagePath = query.record().value("image").toString();
        QString iconPath = query.record().value("icon").toString();

//        qDebug() << "query.record().value(image).toString():" << filepath;

        Event event(EventString(occur, format));
        event.setName(name);
        event.setDefinition(definition);
        event.setImagePath(imagePath);
        event.setIconPath(iconPath);
        eventList.append(event);

//        qDebug() << "event.imagePath():" << event.imagePath();

//        qDebug() << "event.format =" << format;
//        qDebug() << "event.occur =" << occur;
//        qDebug() << "event.begin(): " << event.begin().toString("yyyy-MM-dd");
//        qDebug() << "event.name(): " << event.name();
    }

    return eventList;
}

EventList_t EventSchedule::getDateSchedule(const QDate &date)
{
    QSqlQuery query;
    EventListParams params(date);

    query.prepare("SELECT * FROM event "
                  "WHERE (format LIKE 'YYYYMMDD%' AND occur LIKE :dateYYYYMMDD) "
                  "OR (format LIKE 'RRRRMMDD%' AND substr(occur, 5, 2) LIKE :dateMMDD1) "
                  "OR (format LIKE 'MMDD%' AND occur LIKE :dateMMDD2) "
                  "OR (format LIKE 'MM%D%' AND occur LIKE :dateMMDD3) "
                  "OR (format LIKE 'N%' AND occur LIKE :dateDDD) "
                  "OR (format LIKE 'O%' AND strftime('%d', date(:orthodoxEasterDate, '+' || ltrim(occur,'0') || ' day')) LIKE :dateMMDD4) "
                  "OR (format LIKE 'E%' AND strftime('%d', date(:catholicEasterDate, '+' || ltrim(occur,'0') || ' day')) LIKE :dateMMDD5) ");
    query.bindValue(":dateYYYYMMDD", params.year() + params.month() + params.dayOfMonth() + "%");
    query.bindValue(":dateMMDD1", params.month() + params.dayOfMonth() + "%");
    query.bindValue(":dateMMDD2", params.month() + params.dayOfMonth() + "%");
    query.bindValue(":dateMMDD3", params.month() + params.dayOfMonth() + "%");
    query.bindValue(":dateMMDD4", params.month() + params.dayOfMonth() + "%");
    query.bindValue(":dateMMDD5", params.month() + params.dayOfMonth() + "%");
    query.bindValue(":dateDDD", params.dayOfYear() + "%");
    query.bindValue(":orthodoxEasterDate", Event::orthodoxEasterDate(date.year()));
    query.bindValue(":catholicEasterDate", Event::catholicEasterDate(date.year()));
    query.exec();

    if (query.lastError().isValid()) {
        qDebug() << query.lastError();
    }

    EventList_t eventList;
    while (query.next()) {
        QString format = query.record().value("format").toString();
        QString occur = query.record().value("occur").toString();
        QString name = query.record().value("name").toString();
        QString definition = query.record().value("definition").toString();
        QString imagePath = query.record().value("image").toString();
        QString iconPath = query.record().value("icon").toString();

//        qDebug() << "query.record().value(image).toString():" << filepath;

        Event event(EventString(occur, format));
        event.setName(name);
        event.setDefinition(definition);
        event.setImagePath(imagePath);
        event.setIconPath(iconPath);
        eventList.append(event);

//        qDebug() << "event.imagePath():" << event.imagePath();

//        qDebug() << "event.format =" << format;
//        qDebug() << "event.occur =" << occur;
//        qDebug() << "event.beginDate(): " << event.beginDate().toString("yyyy-MM-dd");
//        qDebug() << "event.name(): " << event.name();
    }

    return eventList;
}

EventList_t EventSchedule::getNotifySchedule(const QDate &date)
{
    QSqlQuery query;
    EventListParams params(date);

    query.prepare("SELECT * FROM event "
                  "WHERE ((format LIKE 'DD%') AND substr(occur, 0, 2) <= :occurDD) "
                  "OR (format LIKE 'MMDD%' AND substr(occur, 0, 4) <= :occurMMDD) "
                  "OR (format LIKE 'YYYYDD%' AND substr(occur, 0, 6) <= :occurYYYYDD) "
                  "OR (format LIKE 'RRRRDD%' AND (strftime('%Y','now') || substr(occur, 5, 2)) <= :occurRRRRDD) "
                  "OR (format LIKE 'YYYYMMDD%' AND substr(occur, 0, 8) <= :occurYYYYMMDD) "
                  "OR (format LIKE 'RRRRMMDD%' AND (strftime('%Y','now') || substr(occur, 5, 4)) <= :occurRRRRMMDD) "
                  "OR ((format LIKE 'WD%') AND substr(occur, 0, 2) <= :occurWD) "
                  "OR ((format LIKE 'MMWD%') AND substr(occur, 0, 4) <= :occurMMWD) "
                  "OR ((format LIKE 'YYYYWD%') AND substr(occur, 0, 6) <= :occurYYYYWD) "
                  "OR ((format LIKE 'RRRRWD%') AND (strftime('%Y','now') || substr(occur, 5, 2)) <= :occurRRRRWD) "
                  "OR ((format LIKE 'YYYYMMWD%') AND substr(occur, 0, 8) <= :occurYYYYMMWD) "
                  "OR ((format LIKE 'RRRRMMWD%') AND (strftime('%Y','now') || substr(occur, 5, 4)) <= :occurRRRRMMWD) "
                  "OR ((format LIKE 'FD%') AND substr(occur, 0, 2) <= :occurFD) "
                  "OR ((format LIKE 'MMFD%') AND substr(occur, 0, 4) <= :occurMMFD) "
                  "OR ((format LIKE 'YYYYFD%') AND substr(occur, 0, 6) <= :occurYYYYFD) "
                  "OR ((format LIKE 'RRRRFD%') AND (strftime('%Y','now') || substr(occur, 5, 2)) <= :occurRRRRFD) "
                  "OR ((format LIKE 'YYYYMMFD%') AND substr(occur, 0, 8) <= :occurYYYYMMFD) "
                  "OR ((format LIKE 'RRRRMMFD%') AND (strftime('%Y','now') || substr(occur, 5, 4)) <= :occurRRRRMMFD) "
                  "OR (format LIKE 'N%' AND occur <= :occurNNN) "
                  "OR (format LIKE 'O%' AND occur <= :orthodoxEasterDate) "
                  "OR (format LIKE 'E%' AND occur <= :catholicEasterDate) ");
    query.bindValue(":occurDD", params.dayOfMonth());
    query.bindValue(":occurMMDD", params.month() + params.dayOfMonth());
    query.bindValue(":occurYYYYDD", params.year() + params.dayOfMonth());
    query.bindValue(":occurRRRRDD", params.currentYear() + params.dayOfMonth());
    query.bindValue(":occurYYYYMMDD", params.year() + params.month() + params.dayOfMonth());
    query.bindValue(":occurRRRRMMDD", params.currentYear() + params.month() + params.dayOfMonth());
    query.bindValue(":occurWD", params.week() + params.dayOfWeek());
    query.bindValue(":occurMMWD", params.month() + params.week() + params.dayOfWeek());
    query.bindValue(":occurYYYYWD", params.year() + params.week() + params.dayOfWeek());
    query.bindValue(":occurRRRRWD", params.currentYear() + params.week() + params.dayOfWeek());
    query.bindValue(":occurYYYYMMWD", params.year() + params.month() + params.week() + params.dayOfWeek());
    query.bindValue(":occurRRRRMMWD", params.currentYear() + params.month() + params.week() + params.dayOfWeek());
    query.bindValue(":occurFD", params.fullWeek() + params.dayOfWeek());
    query.bindValue(":occurMMFD", params.month() + params.fullWeek() + params.dayOfWeek());
    query.bindValue(":occurYYYYFD", params.year() + params.fullWeek() + params.dayOfWeek());
    query.bindValue(":occurRRRRFD", params.currentYear() + params.fullWeek() + params.dayOfWeek());
    query.bindValue(":occurYYYYMMFD", params.year() + params.month() + params.fullWeek() + params.dayOfWeek());
    query.bindValue(":occurRRRRMMFD", params.currentYear() + params.month() + params.fullWeek() + params.dayOfWeek());
    query.bindValue(":occurNNN", params.dayOfYear());
    query.bindValue(":orthodoxEasterDate", params.dayFromOrthEaster());
    query.bindValue(":catholicEasterDate", params.dayFromCathEaster());
    query.exec();

    if (query.lastError().isValid()) {
        qDebug() << query.lastError();
    }

    EventList_t eventList;
    while (query.next()) {
        QString format = query.record().value("format").toString();
        QString occur = query.record().value("occur").toString();
        QString name = query.record().value("name").toString();
        QString definition = query.record().value("definition").toString();
        QString imagePath = query.record().value("image").toString();
        QString iconPath = query.record().value("icon").toString();

//        qDebug() << "query.record().value(image).toString():" << filepath;

        Event event(EventString(occur, format));
        event.setName(name);
        event.setDefinition(definition);
        event.setImagePath(imagePath);
        event.setIconPath(iconPath);
        eventList.append(event);

//        qDebug() << "event.imagePath():" << event.imagePath();

//        qDebug() << "event.format =" << format;
//        qDebug() << "event.occur =" << occur;
//        qDebug() << "event.beginDate(): " << event.beginDate().toString("yyyy-MM-dd");
//        qDebug() << "event.name(): " << event.name();
    }

    return eventList;
}

// ************************************************************

EventListParams::EventListParams(int year, int month)
{
    EventListParams(QDate(year, month, 1));
}

EventListParams::EventListParams(const QDate &date)
{
    int dayOfYear = date.dayOfYear();
    int endDayOfYear = dayOfYear + (date.daysInMonth() - 1);

    m_year = prepareYear(date.year());
    m_currentYear = prepareYear(QDate::currentDate().year());
    m_month = prepareMonth(date.month());

    QDate firstDate(date.year(), date.month(), 1);
    // Узнаём количество дней оставшихся до полной недели
    int daysToFirstFullWeek = 7 - firstDate.dayOfWeek() % 7;
    if (daysToFirstFullWeek == 7) {
        daysToFirstFullWeek = 0;
    }

    // Вычисляем конец первой недели
    QDate firstWeekEndDate = firstDate.addDays(daysToFirstFullWeek);
    int firstWeekEndDay = firstWeekEndDate.day();

    int week = 0;
    int fullWeek = 0;
    int offset = date.day() - firstWeekEndDay;
    if (offset > 0) {
        week = 1 + offset / 7 + (offset % 7 > 0);
    } else {
        week = 1;
        fullWeek = week + 1;
    }

    if (!fullWeek) {
        int daysToFullWeek = 7 - date.dayOfWeek();
        if (date.addDays(daysToFullWeek + 1).month() == date.month()) {
            fullWeek = week;
        } else {
            fullWeek = week - 1;
        }
    }

    m_week = QString::number(week);
    m_fullWeek = QString::number(fullWeek);
    m_dayOfWeek = QString::number(date.dayOfWeek());
    m_dayOfMonth = prepareDay(date.day());
    m_dayOfYear = prepareDayOfYear(dayOfYear);
    m_endDayOfYear = prepareDayOfYear(endDayOfYear);
    int dayFromOrthEaster = date.dayOfYear() - Event::orthodoxEasterDate(date.year()).dayOfYear();
    int dayFromCathEaster = date.dayOfYear() - Event::catholicEasterDate(date.year()).dayOfYear();
    m_dayFromOrthEaster = prepareDayOfYear(dayFromOrthEaster);
    m_dayFromCathEaster = prepareDayOfYear(dayFromCathEaster);
}

QString EventListParams::prepareYear(int year)
{
    QString yearStr = QString::number(year);
    while (yearStr.size() < 4) {
        yearStr.prepend("0");
    }
    if (yearStr.at(0) == '0') {
        yearStr.replace(0, 1, "2");
    }
    return yearStr;
}

QString EventListParams::prepareMonth(int month)
{
    QString monthStr = (month < 10) ? ("0" + QString::number(month)) : QString::number(month);
    return monthStr;
}

QString EventListParams::prepareDay(int day)
{
    QString monthStr = (day < 10) ? ("0" + QString::number(day)) : QString::number(day);
    return monthStr;
}

QString EventListParams::prepareDayOfYear(int day)
{
    QString dayStr = QString::number(day);
    while (dayStr.size() < 3) {
        dayStr.prepend("0");
    }
    return dayStr;
}

//void EventList::append(const Event &t)
//{
////    if (empty()) {
////        current = &t;
////    }
//    QList<Event>::append(t);
//}

//void EventList::prepend(const Event &t)
//{
////    if (empty()) {
////        current = &t;
////    }
//    QList<Event>::prepend(t);
//}

//void EventList::clear()
//{
//    current = -1;
//    QList<Event>::clear();
//}

//void EventList::select(int i)
//{
//    qDebug() << "i = " << i;
//    if (i < size()) {
//        current = i;
//    }
//    qDebug() << "current = " << current;
//}

//void EventList::replace(int i, const Event &t)
//{
//    QList<Event>::replace(i, t);
//}

//void EventList::removeAt(int i)
//{
//    QList<Event>::removeAt(i);
//}

//int EventList::removeAll(const Event &t)
//{
//    return QList<Event>::removeAll(t);
//}

//bool EventList::removeOne(const Event &t)
//{
//    return QList<Event>::removeOne(t);
//}

//Event EventList::takeAt(int i)
//{
//    return QList<Event>::takeAt(i);
//}

//Event EventList::takeFirst()
//{
//    return QList<Event>::takeFirst();
//}

//Event EventList::takeLast()
//{
//    return QList<Event>::takeLast();
//}

//void EventList::move(int from, int to)
//{
//    QList<Event>::move(from, to);
//}

//void EventList::swap(int i, int j)
//{
//    QList<Event>::swap(i, j);
//}
